<?php
/**
 * Author: Traveler
 * Time  : 2021/9/4 19:17
 */

namespace app\service;


use app\ErrorCode;
use app\model\RoleModel;
use sunshine\basic\RestService;
use sunshine\exceptions\ApiException;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;

class RoleService extends RestService
{
    /**
     * RoleService constructor.
     */
    public function __construct()
    {
        $this->model = new RoleModel();
        $this->initialize();
    }

    /**
     * 初始化
     */
    public function initialize()
    {
        $this->queryConditions['with'] = ['menus'];
    }

    /**
     * 删除角色(重写)
     *
     * @param int        $id 角色id
     * @param array|null $relation
     * @return bool
     * @throws DataNotFoundException
     * @throws DbException
     * @throws ModelNotFoundException
     */
    public function delete(int $id, ?array $relation = null): bool
    {
        // 检查角色是否在使用中
        $staffService = new StaffService();
        if ($staffService->getCountByRole($id) > 0) {
            throw new ApiException(ErrorCode::ROLE_USING);
        }

        return parent::delete($id);
    }

    /**
     * 分配权限
     *
     * @param int   $id   角色ID
     * @param array $data 请求数据
     * @return bool
     */
    public function assignPermission(int $id, array $data): bool
    {
        $role = $this->model->find($id);
        if (!$role) {
            throw new ApiException(ErrorCode::DATA_NOT_FOUND);
        }

        $menuIds = $data['menuIds'];

        if (is_array($menuIds)) {
            $role->menus()->sync($menuIds, true);
            return true;
        }

        if (is_string($menuIds) && false === strpos(',', $menuIds)) {
            $ids = explode(',', $menuIds);
            $role->menus()->sync($ids, true);
            return true;
        }

        return false;
    }

    /**
     * 获取角色对应的全部菜单数据
     *
     * @param int $id 角色ID
     * @return array
     */
    public
    function getRoleMenus(int $id): array
    {
        // 超级管理员始终拥有所有权限
        if (1 === $id) {
            return list_to_tree((new MenuService())->getAllMenus());
        }

        // 其他角色
        $menuTree = list_to_tree($this->model->find($id)->menus()->select()->toArray());
        $this->filterMenus($menuTree);

        return $menuTree;
    }

    /**
     * 过滤被禁用的菜单及其子项
     *
     * @param array $menuTree 菜单树
     */
    private
    function filterMenus(array &$menuTree)
    {
        // 元素必须为引用&
        foreach ($menuTree as $key => &$menu) {
            // 查找被禁用的节点
            // 如果父节点是被禁用状态，则没有必要再递归子节点，整体移除即可
            if (0 === $menu['status']) {
                unset($menuTree[$key]);
                continue;
            }

            // 递归子节点
            $hasChildren = false;
            if (array_key_exists('children', $menu) && $menu['children']) {
                $hasChildren = true;
                $this->filterMenus($menu['children']);
            }

            // hasChildren代表该父节点原本有子节点，防止无子节点的父节点被误杀
            // 所有子节点都被过滤，则该父节点也应该被过滤
            // 否则菜单中会遗留单独的父级菜单
            if ($hasChildren && (!isset($menu['children']) || !$menu['children'])) {
                unset($menuTree[$key]);
            }
        }

        // 销毁引用
        // foreach中的引用会被保留，需要手动销毁
        // 参考：https://www.php.net/manual/zh/control-structures.foreach.php
        unset($menu);

        // 将过滤后的数组下标恢复连续，否则前端读取会出错
        $menuTree = array_values($menuTree);
    }
}